package mul.Logic;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Random;
import java.util.Timer;
import mul.UI.SoundManager;
import android.content.res.Resources;


import android.util.Log;
import android.util.Pair;

public class GameState {
	
	private final int ROWS = 6;
	private Level level;
	private ArrayList<ArrayList<Crate>> crateColumns;

	private HighScore score;

	private Random random = new Random();
	private int w,h;
	private Timer spawnTimer;
	private Timer levelTimer;
	private Timer multiplierTimer;
	private Timer freezeTimer;
	private Timer countdownTimer;
	private int remainingLevelTime;
	private int multiplier;
	private double remainingMultiplierPercentage;
	private boolean levelActive = true;
	private int markedCratesNum;
	private int markedGoldCratesNum;
	private int markedBlackCratesNum;
	public Crate lastTouchedCrate;
	private int oldCol, oldRow, actualOldCol;
	private ArrayList<String> oldPosStack;
	private boolean[][] markedCrates;
	private ArrayList<Level> levels = new ArrayList<Level>();
	private int currentLevel = 0;
	private ArrayList<Sprite> spriteList;
	private Resources resources;
//	private SoundManager soundManager;
	public boolean gameOver = false;
	public boolean gameComplete = false;
	private boolean isNewLevel = false; 
	private int inARow = 0;
	private int pendingGoldCrates = 0;
	private boolean isFrozen = false;
	private boolean levelCountdown = false;
	private int levelCountdownValue = 0;
	private ArrayList<Pair<Float, Float>> explosionPositions;
	private boolean showLetsGo = false;
	
	public GameState(int w, int h, InputStream levelStream, ArrayList<Sprite> spriteList, Resources resources){//, SoundManager soundManager){
		this.score = new HighScore(w/2, 35);
		parseLevels(levelStream);
		level = levels.get(currentLevel);
		currentLevel++;
		this.w = w;
		this.h = h;
		
		levelSetup();
		
		oldRow = -1;
		oldCol = -1;
		oldPosStack = new ArrayList<String>();
		markedCrates = new boolean[5][6];
//		}
		this.spriteList = spriteList;
		this.resources = resources;
//		this.soundManager = soundManager;
		this.explosionPositions = new ArrayList<Pair<Float, Float>>();
	}
	
	private void levelSetup() {
		isNewLevel = true;
		remainingLevelTime = level.getLevelTime();
		remainingMultiplierPercentage = 0.0;
		multiplier = 1;
		inARow = 0;
		pendingGoldCrates = 0;
		isFrozen = false;
		levelCountdown = true;
		levelCountdownValue = 3;
		
		markedCratesNum = 0;
		markedGoldCratesNum = 0;
		markedBlackCratesNum = 0;
		
		crateColumns = new ArrayList<ArrayList<Crate>>();
		for(int i = 0; i < 5; i++) // Create the empty columns
			crateColumns.add(new ArrayList<Crate>());
		
		for(int i = 0; i < 5; i++) {
			ArrayList<Character> column = level.getColumn(i);
			
			for(int j = 0; j < column.size(); j++) {
				switch(column.get(j)) {
					case '?':
						addCrate(i, level.getRandomCrate());
						break;
					case '.':
						break;
					case 'T':
						addCrate(i, 10);
						break;
					case 'I':
						addCrate(i, level.getRandomCrate(), Crate.FROST_CRATE);
						break;
					case 'F':
						addCrate(i, level.getRandomCrate(), Crate.FIRE_CRATE);
						break;
					default:
						addCrate(i, column.get(j)-'0');
						break;
				}
			}
		}
		
		levelTimer = new Timer();
		levelTimer.schedule(new LevelTimer(this), 1000, 1000);
		
		countdownTimer = new Timer();
		countdownTimer.schedule(new CountdownTimer(this), 1000, 1000);
	}
	
	public void startSpawnTimer() {
		spawnTimer = new Timer();
		spawnTimer.schedule(new SpawnTimer(this), (long)(level.getSpawningRate()*1000), (long)(level.getSpawningRate()*1000));
	}
	
	public void setLevelCountdownValue(int value) {
		levelCountdownValue = value;
	}
	
	public int getLevelCountdownValue() {
		return levelCountdownValue;
	}
	
	public void setLevelCountdown(boolean value) {
		levelCountdown = value;
	}
	
	public boolean isLevelCountdown() {
		return levelCountdown;
	}
	
	public void setIsNewLevel(boolean value) {
		isNewLevel = value;
	}
	
	public boolean isNewLevel() {
		return isNewLevel;
	}
	
	public void incrementInARow() {
		inARow++;
		if(inARow % 5 == 0)
			pendingGoldCrates++;
	}
	
	public void resetInARow() {
		inARow = 0;
	}
	
	public int getInARow() {
		return inARow;
	}
	
	private void parseLevels(InputStream levelStream) {
		try {
			BufferedReader br = new BufferedReader(new InputStreamReader(levelStream));
			String line;
			while((line = br.readLine()) != null) {
				String[] pieces = line.split(" ");
				String levelName = pieces[0];
				int levelTime = Integer.parseInt(pieces[1]);
				double spawnRate = Double.parseDouble(pieces[2]);
				int speed = Integer.parseInt(pieces[3]);
				int frostCrateProb = 0;
				if(pieces.length >= 5)
					frostCrateProb = Integer.parseInt(pieces[4]);
				int fireCrateProb = 0;
				if(pieces.length >= 6)
					fireCrateProb = Integer.parseInt(pieces[5]);
					
				Level newLevel = new Level(levelName, levelTime, spawnRate, speed);
				levels.add(newLevel);
				newLevel.setFrostCrateProbability(frostCrateProb);
				newLevel.setFireCrateProbability(fireCrateProb);
				String probabilityLine = br.readLine();
				String[] probabilityPieces = probabilityLine.split(" ");
				for(int i = 0; i < probabilityPieces.length; i++)
					newLevel.setSpawnProbability(i, Integer.parseInt(probabilityPieces[i]));
				
				String levelLine;
				while(!(levelLine = br.readLine()).equals("---")) {
						for(int i = 0; i < levelLine.length(); i++) {
							newLevel.addCrate(i, levelLine.charAt(i));
						}
				}
				
			}
		}
		catch(FileNotFoundException fnfe) {
			System.err.println("File not found!");
		}
		catch(IOException ioe) {
			System.err.println("Error with IO!");
		}
	}
	
	public void resetMultiplier() {
		multiplier = 1;
		remainingMultiplierPercentage = 0.0;
		if(multiplierTimer != null)
			multiplierTimer.cancel();
	}
	
	public int getMultiplier() {
		return multiplier;
	}
	
	public double getRemainingMultiplierPercentage() {
		return remainingMultiplierPercentage;
	}
	
	public void setRemainingMultiplierPercentage(double p) {
		remainingMultiplierPercentage = p;
	}
	
	public String getFormattedMultiplier() {
		return multiplier+"x";
	}
	
	public void incrementMultiplier() {
		multiplier++;
		if(multiplierTimer != null)
			multiplierTimer.cancel();
		multiplierTimer = new Timer();
		multiplierTimer.schedule(new MultiplierTimer(this), 100, 100);
	}
	
	public ArrayList<ArrayList<Crate>> getCrates(){
		return crateColumns;
	}
	
	public void addCrate(int column, int number, int type) {
		Crate c = new Crate(number, column*w/5, h-(w/5 * (crateColumns.get(column).size()+1)), column);
		c.setMoving(false);
		c.setCrateType(type);
		crateColumns.get(column).add(c);
	}
	
	public void addCrate(int column, int number) {
		addCrate(column, number, Crate.NORMAL_CRATE);
	}
	
	public void addCrate(int column) {
		addCrate(column, (int)(random.nextDouble()*11));
	}
	
	public synchronized void spawnCrate(int type) {
		int column = (int)(random.nextDouble()*5);
		while(crateColumns.get(column).size() > 0 && crateColumns.get(column).get(crateColumns.get(column).size()-1).getYpos() < -50 + w/5) // avoid collision when spawning
			column = (int)(random.nextDouble()*5);
		Crate c = new Crate(level.getRandomCrate(), column*w/5, -50, column);
		c.setCrateType(type);
		crateColumns.get(column).add(c);
		
		if(crateColumns.get(column).size() == 7)
		{
			gameOver = true;
			levelStop();
		}
	}
	
	public synchronized void spawnCrate() {
		if(pendingGoldCrates > 0) {
			spawnCrate(Crate.GOLD_CRATE);
			pendingGoldCrates--;
		}
		else {
			int r = (int)(Math.random()*100);
			if(level.getFrostCrateProbability() > r) {
				spawnCrate(Crate.FROST_CRATE);
				return;
			}
			
			r = (int)(Math.random()*100);
			if(level.getFireCrateProbability() > r) {
				spawnCrate(Crate.FIRE_CRATE);
				return;
			}
			
			spawnCrate(Crate.NORMAL_CRATE);
		}
	}
	
	public synchronized void levelStop() {
		levelActive = false;
		spawnTimer.cancel();
		levelTimer.cancel();
		if(multiplierTimer != null)
			multiplierTimer.cancel();
	}
	
	public synchronized void levelNext() {
		levelStop();
		if(currentLevel < levels.size()) {
			level = levels.get(currentLevel);
			currentLevel++;
			levelSetup();
		}
		else {
			gameComplete = true;
			levelStop();
		}
	}
	
	public int getTimeleft() {
		return remainingLevelTime;
	}
	
	public String getFormattedTimeleft() {
		int minutes = remainingLevelTime / 60;
		int seconds = remainingLevelTime % 60;
		DecimalFormat dfm = new DecimalFormat("0");
		DecimalFormat dfs = new DecimalFormat("00");
		return dfm.format(minutes)+":"+dfs.format(seconds);
	}
	
	public void decrementTimeleft() {
		remainingLevelTime--;
	}
	
	private int eNormal = 0;
	private int eGold = 0;
	private int eBlack = 0;
	private boolean[][] fireCrateDestruction = null;
	
	private void updateExplodedStats(int col, int row) {
		if(crateColumns.get(col).get(row).isGoldCrate())
			eGold++;
		else if(crateColumns.get(col).get(row).isBlackCrate())
			eBlack++;
		else
			eNormal++;
	}
	
	public void explodeCrates() {
		int cratesExploded = 0;
		eNormal = 0;
		eGold = 0;
		eBlack = 0;
		for(int x = 0; x < 2; x++) // Iterate over once to look for fire crates, then do other crates
			for(int i = 0; i < crateColumns.size(); i++) {
				for(int j = crateColumns.get(i).size()-1; j >= 0; j--) {
					if(j > crateColumns.get(i).size()-1)
						continue;
					
					if(crateColumns.get(i).get(j).isMarked()) {
						if(x == 0 && crateColumns.get(i).get(j).isFireCrate()) {
							fireCrateDestruction = new boolean[5][7];
							markNearby(i,j);
							cratesExploded += destroyFireCrate();
						}
						else if(x == 1) {
							cratesExploded++;
							updateExplodedStats(i, j);
							this.addExplosionPosition(crateColumns.get(i).get(j));
							if(crateColumns.get(i).get(j).isFrostCrate()){
								freeze();	
							}
							crateColumns.get(i).remove(j);
							Log.w("debugging", "removed");
						}

						for(int k = j; x == 1 && k < crateColumns.get(i).size(); k++) {
							crateColumns.get(i).get(k).setMoving(true);
						}
					}
				}
			}
//		this.soundManager.playCrateExplosion();
		this.addScore(eNormal, eGold, eBlack);
		for(int i = 1; i < cratesExploded; i++)
			incrementMultiplier();
	}
	
	private void markNearby(int col, int row) {
		fireCrateDestruction[col][row] = true;
		
		if(crateColumns.get(col).size() > row+1 && !fireCrateDestruction[col][row+1] && !crateColumns.get(col).get(row+1).isMoving()) // up
			if(crateColumns.get(col).get(row+1).isFireCrate())
				markNearby(col, row+1);
			else
				fireCrateDestruction[col][row+1] = true;
		
		if(row > 0 && !fireCrateDestruction[col][row-1]) // down
			if(crateColumns.get(col).get(row-1).isFireCrate())
				markNearby(col, row-1);
			else
				fireCrateDestruction[col][row-1] = true;
		
		if(col > 0 && crateColumns.get(col-1).size() > row && !fireCrateDestruction[col-1][row] && !crateColumns.get(col-1).get(row).isMoving()) // left
			if(crateColumns.get(col-1).get(row).isFireCrate())
				markNearby(col-1, row);
			else
				fireCrateDestruction[col-1][row] = true;
		
		if(crateColumns.size() > col+1 && crateColumns.get(col+1).size() > row && !fireCrateDestruction[col+1][row] && !crateColumns.get(col+1).get(row).isMoving()) // right
			if(crateColumns.get(col+1).get(row).isFireCrate())
				markNearby(col+1, row);
			else
				fireCrateDestruction[col+1][row] = true;
	}
	
	public int destroyFireCrate() {
		int destroyed = 0;
		
		for(int i = 0; i < crateColumns.size(); i++) {
			for(int j = crateColumns.get(i).size(); j >= 0; j--) {
				if(fireCrateDestruction[i][j]) {
					destroyed++;
					updateExplodedStats(i,j);
					crateColumns.get(i).remove(j);
					for(int k = j; k < crateColumns.get(i).size(); k++)
						crateColumns.get(i).get(k).setMoving(true);
				}
			}
		}
		
		return destroyed;
	}
	
	private void freeze() {
		isFrozen = true;
		if(freezeTimer != null)
			freezeTimer.cancel();
		freezeTimer = new Timer();
		freezeTimer.schedule(new FreezeTimer(this), 100, 100);
	}
	
	public boolean isFrozen() {
		return isFrozen;
	}
	
	public void setNotFrozen() {
		isFrozen = false;
	}
	
	public HighScore getScore(){
		return this.score;
	}

	public void addScore(int normalCrates, int goldCrates, int blackCrates) {
		int nc = 250 * normalCrates;
		int gc = 1000 * goldCrates;
		int bc = 0 * blackCrates;
		score.addScoreWithMultiplier(nc+gc+bc, getMultiplier(), goldCrates > 0);
	}
			
	
	public synchronized void checkTouch(float x, float y, boolean actionDown){
		for(int i=0; i<5; i++){
			if(x<w*(i+1)/5){ //for all columns
				if(crateColumns.get(i).size() != 0){
					for(int k=0; k<ROWS; k++){ //find touched crate
						if(crateColumns.get(i).size() >= k+1){
							if(y>h-(k+1)*w/5){
								if(!crateColumns.get(i).get(k).isMoving()){
//									Log.w("checkTouch", "-------------------------------------------");
									if(!actionDown && oldRow==k && oldCol==i){
//										Log.w("checkTouch", "!actionDown");
										return;
									}
									if(markedCratesNum>=3){
										Log.w("checkTouch", "num>3");
										if(crateColumns.get(i).get(k).isMarked()){
//											Log.w("checkTouch", "num>3 && marked");
											markCrate(oldCol, oldRow);
											markCrate(i, k);
											return;
										}
										return;
									}
									else{
										if(markedCratesNum==0){
//											Log.w("checkTouch", "num==0");
											markCrate(i, k);
											return;
										}
										if(markedCratesNum==1 && actionDown && markedCrates[i][k]==true){
//											Log.w("checkTouch", "num==1");
											markCrate(i, k);
											return;
										}
										if(i<4){
											Log.w("checkTouch", "i < 4");
											if(markedCrates[i+1][k]){
//												Log.w("checkTouch", "i < 4   inside");
												markCrate(i, k);
												return;
											}
										}
										if(i>0){
											Log.w("checkTouch", "i > 0");
											if(markedCrates[i-1][k]){
//												Log.w("checkTouch", "i > 0   inside");
												markCrate(i, k);
												return;
											}
										}
										if(k>0){
											Log.w("checkTouch", "k > 0");
											if(markedCrates[i][k-1]){
//												Log.w("checkTouch", "k > 0   inside");
												markCrate(i, k);
												return;
											}
										}
										if(k<crateColumns.get(i).size() && k<5){
											Log.w("checkTouch", "siste if setning");
											Log.w("checkTouch", "i: " +i +"   k: " +k);
											if(markedCrates[i][k+1]){ // TODO: ArrayIndexOutOfBounds?...hopefully fixed
//												Log.w("checkTouch", "siste if setning    inside");
												markCrate(i, k);
												return;
											}	
										}
//										Log.w("checkTouch", "slutten");
//										Log.w("checkTouch", "i: " +i + "  k: "+k+ "  num: "+markedCratesNum);
									}
								}
								return;
							}
						}
					}
					break;
				}
			}
		}
	}
	
	public void checkClick(float x, float y) {
		for(int i = 0; i < crateColumns.size(); i++)
			for(int j = 0; j < crateColumns.get(i).size(); j++)
				if(crateColumns.get(i).get(j).isMoving() && crateColumns.get(i).get(j).getXpos() <= x && crateColumns.get(i).get(j).getXpos()+w/5 >= x && crateColumns.get(i).get(j).getYpos() <= y && crateColumns.get(i).get(j).getYpos()+w/5 >= y)
					crateColumns.get(i).get(j).setClicked(true);
	}
	
	private synchronized void markCrate(int i, int k){
		if(crateColumns.get(i).get(k).isMarked()){
			crateColumns.get(i).get(k).setMarked(false);
			markedCrates[i][k] = false;
			if(markedCratesNum>0){
				markedCratesNum--;
				
				if(crateColumns.get(i).get(k).isGoldCrate())
					markedGoldCratesNum--;
				else if(crateColumns.get(i).get(k).isBlackCrate())
					markedBlackCratesNum--;
			}
//			else if(markedCratesNum==0){
//				Log.w("checkTouch", "markedCratesNum-- when ==0");
//			}
		}
		else{
			crateColumns.get(i).get(k).setMarked(true);
			markedCrates[i][k] = true;
			markedCratesNum++;
			if(crateColumns.get(i).get(k).isGoldCrate())
				markedGoldCratesNum++;
			else if(crateColumns.get(i).get(k).isBlackCrate())
				markedBlackCratesNum++;
			
		}
		oldRow = k;
		oldCol = i;
		Log.w("checkTouch", "num: "+markedCratesNum);
	}
	
	public int getLevelDropSpeed(){
		return level.getSpeed();
	}
	
	public String getLevelName() {
		return level.getName();
	}
	
	public int getMarkedCratesNum(){
		return markedCratesNum;
	}
	
	public int getMarkedGoldCratesNum(){
		return markedGoldCratesNum;
	}
	
	public int getMarkedBlackCratesNum(){
		return markedBlackCratesNum;
	}
	
	public void resetMarkedCratesNum(){
		markedCratesNum = 0;
		markedGoldCratesNum = 0;
		markedBlackCratesNum = 0;
		oldCol = -1;
		oldRow = -1;
		markedCrates = new boolean[5][6];
		Log.w("checkTouch", "resetted successfully");
	}
	
	public ArrayList<Pair<Float, Float>> getExplosionPositions(){
		return this.explosionPositions;
	}
	private void addExplosionPosition(Crate crate){
		Pair<Float, Float> cratePos = Pair.create(crate.getXpos(), crate.getYpos());
		this.explosionPositions.add(cratePos);
	}
	public void clearExplosionPositions(){
		this.explosionPositions.clear();
	}
	public void setLetsGo(boolean show){
		this.showLetsGo = show;
	}
	public boolean getLetsGo(){
		return this.showLetsGo;
	}
}
